import math
import os
import random
import struct
from datetime import datetime, timedelta


class TSData:
    def __init__(self, file_name, data_dir: str = None, value_size = 1, second_step = 1, empty_value = 0, invild_value = None):
        if isinstance(file_name, datetime):
            file_name = TSData.generate_filename(file_name, data_dir)
        self.file_name = file_name
        self.data_dir = data_dir
        params = self.read_head()
        if params is not None:
            self.init(params[0], params[1], params[2], params[3], params[4], params[5], params[6])
        else:
            self.init(0, value_size=value_size, second_step=second_step, multiple=1, value_offset=0, empty_value=empty_value, invild_value=invild_value)

    def init(self, head_size = 0, value_size = 1, second_step = 1, multiple = 1, value_offset = 0, empty_value = 0, invild_value = None):
        # 定义常量
        self.HEAD_SIZE = head_size  # 文件头大小
        self.SECOND_STEP = second_step  # 时间间隔，单位为秒
        self.MULTIPLE = multiple  # 倍数
        self.VALUE_OFFSET = value_offset  # 偏移值
        self.SECONDS_IN_DAY = 86400 # 一天的总秒数
        self.DATA_COUNT_IN_DAY = self.SECONDS_IN_DAY // self.SECOND_STEP # 一天的数据块数量
        self.VALUE_SIZE = value_size  # 每个数据块字节大小
        self.VALUE_RANGE = 2**(8*self.VALUE_SIZE) - 1  # 数据值范围
        self.DATA_SIZE = self.VALUE_SIZE * self.DATA_COUNT_IN_DAY  # 一天的数据总大小
        self.FILE_SIZE = self.HEAD_SIZE + self.DATA_SIZE  # 一天的文件总大小为
        if invild_value is None:
            self.INVILD_VALUE = self.VALUE_RANGE # 无效值，默认最大值为无效值
        else:
            self.INVILD_VALUE = invild_value
        self.EMPTY_VALUE = empty_value # 空值，默认0为空值

    def read_head(self):
        # 如果文件不存在，说明文件不合法，直接返回None
        if not os.path.exists(self.file_name):
            return None
        # 如果文件小于六个字节，说明文件不合法，直接返回None
        file_size = os.path.getsize(self.file_name)
        if file_size < 6:
            return None
        # 读取文件头
        with open(self.file_name, 'rb') as f:
            # 读取前四个字节，判断是否为文件头标识
            head = f.read(4)
            if head != b'\xfa\xfb\xfc\xfd':
                return None
            # 读取两个字节，表示文件头大小
            head_size = int.from_bytes(f.read(2), 'big')
            if file_size < head_size:
                return None
            # 继续读取两个字节，表示数据块大小（字节）
            value_size = int.from_bytes(f.read(2), 'big')
            # 继续读取两个字节，表示时间间隔（秒）
            second_step = int.from_bytes(f.read(2), 'big')
            # 继续读取两个字节，表示倍数
            multiple = int.from_bytes(f.read(2), 'big')
            # 继续读取数据块大小的字节，表示偏移值
            value_offset = int.from_bytes(f.read(value_size), 'big')
            # 继续读取数据块大小的字节，表示无效值
            invild_value = int.from_bytes(f.read(value_size), 'big')
            # 继续读取数据块大小的字节，表示空值
            empty_value = int.from_bytes(f.read(value_size), 'big')
            return (head_size, value_size, second_step, multiple, value_offset, empty_value, invild_value)

    def generate_head(self):
        # 生成文件头
        self.HEAD_SIZE = 12 + self.VALUE_SIZE * 3 # 计算并修改文件头大小
        head = b'\xfa\xfb\xfc\xfd'  # 文件头标识
        head += self.HEAD_SIZE.to_bytes(2, 'big')  # 文件头大小
        head += self.VALUE_SIZE.to_bytes(2, 'big')  # 数据块大小（字节）
        head += self.SECOND_STEP.to_bytes(2, 'big')  # 时间间隔（秒）
        head += self.MULTIPLE.to_bytes(2, 'big')  # 倍数
        head += self.VALUE_OFFSET.to_bytes(self.VALUE_SIZE, 'big')  # 偏移值
        head += self.INVILD_VALUE.to_bytes(self.VALUE_SIZE, 'big')  # 无效值
        head += self.EMPTY_VALUE.to_bytes(self.VALUE_SIZE, 'big')  # 空值
        return head


    @staticmethod
    def today(data_dir: str = None, value_size = 1, second_step = 1, empty_value = 0, invild_value = None):
        return TSData(TSData.generate_filename(datetime.now(), data_dir, value_size, second_step, empty_value, invild_value))

    # 生成文件名
    @staticmethod
    def generate_filename(date, data_dir: str = None):
        if data_dir is not None:
            return os.path.join(data_dir, f'{date.strftime("%Y-%m-%d")}.bin')
        else:
            return f'{date.strftime("%Y-%m-%d")}.bin'

    @staticmethod
    def write_now(value: int, data_dir: str = None, value_size = 1, second_step = 1, empty_value = 0, invild_value = None):
        now = datetime.now()
        # 计算当前在一天中的总秒数
        seconds_tody = TSData.calculate_second(now)
        # 生成文件名
        file_name = TSData.generate_filename(now, data_dir)
        tsdata = TSData(file_name, data_dir, value_size, second_step, empty_value, invild_value)
        tsdata.write(seconds_tody, value)

    # 将数据写到当前秒数位置
    def write(self, second: int, value: int):
        if second < 0 or second >= self.SECONDS_IN_DAY:
            raise ValueError(f"Second {second} is out of range (0-{self.SECONDS_IN_DAY})")
        if value < 0 or value > self.VALUE_RANGE:
            raise ValueError(f"Value {value} is out of range (0-{self.VALUE_RANGE})")
        # second必须是SECOND_STEP的整数倍，如果不是，则需要就近取整
        if second % self.SECOND_STEP!= 0:
            # 计算最接近的秒数
            second = math.ceil(second / self.SECOND_STEP) * self.SECOND_STEP
        # if second % self.SECOND_STEP != 0:
        #     raise ValueError(f"Second {second} is not a multiple of {self.SECOND_STEP}")

        file_name = self.file_name
        # 检查文件是否存在，如果不存在则创建
        if not os.path.exists(file_name):
            os.makedirs(os.path.dirname(file_name), exist_ok=True)
            with open(file_name, 'wb') as f:
                f.write(self.generate_head() + b'\xff' * self.DATA_SIZE)
        
        # 将值写入到文件
        with open(file_name, 'r+b') as f:
            # 计算偏移量（每个数据块为2字节）
            offset = self.HEAD_SIZE + (second // self.SECOND_STEP) * self.VALUE_SIZE
            # 移动文件指针到指定位置
            f.seek(offset)
            # 写入数据
            # value = value // self.MULTIPLE + self.VALUE_OFFSET
            data = value.to_bytes(self.VALUE_SIZE, 'big')
            f.write(data)
            # 打印当前状态（可选）
            # print(f"Second {second}: Data {str(value)} written at offset {offset}")

    @staticmethod
    def read_today(second: int, data_dir: str = None, value_size = 1, second_step = 1, empty_value = 0, invild_value = None):
        now = datetime.now()
        file_name = TSData.generate_filename(now, data_dir)
        tsdata = TSData(file_name, data_dir, value_size, second_step, empty_value, invild_value)
        return tsdata.read(second)
    
    # 读取文件指定秒数位置的数据
    def read(self, second: int):
        if second < 0 or second >= self.SECONDS_IN_DAY:
            raise ValueError(f"Second {second} is out of range (0-{self.SECONDS_IN_DAY})")
        if not os.path.exists(self.file_name):
            return self.INVILD_VALUE
        with open(self.file_name, 'rb') as f:
            # 计算偏移量
            offset = self.HEAD_SIZE + (second // self.SECOND_STEP) * self.VALUE_SIZE
            # 移动文件指针到指定位置
            f.seek(offset)
            # 读取数据
            data = f.read(self.VALUE_SIZE)
            # 将数据转换为整数
            value = int.from_bytes(data, 'big')
            # if value == self.INVILD_VALUE:
            #     return self.INVILD_VALUE
            # if value == self.EMPTY_VALUE:
            #     return self.EMPTY_VALUE
            # value = (value - self.VALUE_OFFSET) * self.MULTIPLE
            return value
    
    # 区间读取多个值，可以跳读
    def read_range(self, start: int, count: int, step: int = 0):
        if step == 0:
            step = self.SECOND_STEP
        # if step < self.SECOND_STEP or step > self.SECONDS_IN_DAY:
        #     raise ValueError(f"Step {step} is out of range ({self.SECOND_STEP}-{self.SECONDS_IN_DAY})")
        less_muti = 1
        if step < self.SECOND_STEP:
            if self.SECOND_STEP % step != 0:
                raise ValueError(f"Step {step} is not a divisor of {self.SECOND_STEP}")
            less_muti = self.SECOND_STEP // step
            step = self.SECOND_STEP
        if step > self.SECONDS_IN_DAY:
            raise ValueError(f"Step {step} is not a divisor of {self.SECONDS_IN_DAY}")
        if self.SECONDS_IN_DAY % step != 0:
            raise ValueError(f"Step {step} is not a divisor of {self.SECONDS_IN_DAY}")

        
        m = start % step
        if m != 0: # 如果start不是step的整数倍，则向前取整
            start -= m
        
        # 计算偏移量
        offset = self.HEAD_SIZE + (start // self.SECOND_STEP) * self.VALUE_SIZE
        read_size = min(self.FILE_SIZE - offset, math.ceil(count / less_muti) * self.VALUE_SIZE * (step // self.SECOND_STEP) ) # 读取数据不能超过当前文件大小
        
        if not os.path.exists(self.file_name):
            return [self.INVILD_VALUE] * (read_size // (step // self.SECOND_STEP) // self.VALUE_SIZE)
        
        values = None
        with open(self.file_name, 'rb') as f:
            # 移动文件指针到指定位置
            f.seek(offset)
            # 读取数据
            data = f.read(read_size)
            # 将数据转换为整数
            values = [int.from_bytes(data[i:i + self.VALUE_SIZE], 'big') for i in range(0, len(data), (step // self.SECOND_STEP) * self.VALUE_SIZE)]
        
        # 如果step小于self.SECOND_STEP，需要将数组扩充为step的倍数，例如原来是[1,2],扩充后可能为[1,invalid,invalid,2,invalid,invalid]
        if less_muti > 1:
            temp = []
            for i in range(len(values)):
                temp.append(values[i])
                for j in range(1, less_muti):
                    temp.append(self.INVILD_VALUE)
            # 如果m > 0，说明需要适当减少数组长度，因为start已经被向前取整过了
            if m!= 0:
                reduce_count = (self.SECOND_STEP - m) // (self.SECOND_STEP // less_muti)
                temp = temp[reduce_count:]
            values = temp[:count]
        
        return values
    
    # 区间读取平均值，相当于count个数相加再除以count得到的平均值，而不是计算count个平均值，并指定是否考虑断线情况
    def read_avg(self, start: int, count: int, consider_break: bool = True):
        values = self.read_range(start, count)
        sum_value = 0
        value_count = 0
        for value in values:
            if value == self.INVILD_VALUE:  # 考虑当前时间不存在数据
                continue
            if consider_break and value == self.EMPTY_VALUE:
                return self.EMPTY_VALUE # 考虑当前时间存在断线情况
            sum_value += value
            value_count += 1
        if value_count == 0:
            return self.INVILD_VALUE
        return sum_value // value_count
    
    # 区间读取多个平均值，读取count个平均值，每个平均值的步长为step秒，考虑断线情况
    def read_range_avg(self, start: int, count: int, step: int = 0, consider_break: bool = True):
        if step == 0:
            step = self.SECOND_STEP
        # if step < self.SECOND_STEP or step > self.SECONDS_IN_DAY:
        #     raise ValueError(f"Step {step} is out of range ({self.SECOND_STEP}-{self.SECONDS_IN_DAY})")
        if step > 3600:
            raise ValueError(f"Step must not be greater than 3600, but got {step}") # 步长不能超过1小时，计算超过一个小时的平局值几乎没有意义
        if self.SECONDS_IN_DAY % step != 0:
            raise ValueError(f"Step {step} is not a divisor of {self.SECONDS_IN_DAY}")
        m = start % step
        if m != 0: # 如果start不是step的整数倍，则向前取整
            start -= m
        # 为了兼容step比second_step小的情况，因此多了个step参数，但当step大于second_step时，依然使用second_step，否则跳读的话计算平均值就没有意义了
        if step > self.SECOND_STEP:
            values = self.read_range(start, count * math.ceil(step / self.SECOND_STEP), self.SECOND_STEP)
        else:
            values = self.read_range(start, count * math.ceil(step / self.SECOND_STEP), step)
        result = []
        # m = len(values) % (step // self.SECOND_STEP)
        # if m != 0:
        #     values = values + [self.INVILD_VALUE] * ((step // self.SECOND_STEP) - m) # 补齐数据，保证数据是step的整数倍
        # print("需要计算平均数的值数量：", math.ceil(step / self.SECOND_STEP), step, self.SECOND_STEP)
        # print(values)
        for i in range(0, len(values), math.ceil(step / self.SECOND_STEP)):
            sum_value = 0
            value_count = 0
            for j in range(i, i + math.ceil(step / self.SECOND_STEP)):
                if j >= len(values):
                    break
                if values[j] == self.INVILD_VALUE:
                    continue
                if consider_break and values[j] == self.EMPTY_VALUE:
                    value_count = -1
                    break
                sum_value += values[j]
                value_count += 1
            if value_count == 0:
                result.append(self.INVILD_VALUE)
            elif value_count == -1: # 考虑当前时间存在断线情况
                result.append(self.EMPTY_VALUE)
            else:
                result.append(sum_value // value_count)
        return result
    
    @staticmethod
    def calculate_second(date: datetime):
        return date.hour * 3600 + date.minute * 60 + date.second
 
    @staticmethod
    def read_dates_range(start: datetime, count: int, step: int = 0, data_dir: str = None, value_size = 1, second_step = 1, empty_value = 0, invild_value = None):
        if count > 1000:
            raise ValueError("Too many data to read, please reduce the range")
        start_second = TSData.calculate_second(start)
        result = TSData(start, data_dir, value_size, second_step, empty_value, invild_value).read_range(start_second, count, step)
        count -= len(result)
        while count > 0:
            start = start + timedelta(days=1)
            result += TSData(start, data_dir, value_size, second_step, empty_value, invild_value).read_range(0, count, step)
            count -= len(result)
        return result

    @staticmethod
    def read_dates_range_avg(start: datetime, count: int, step: int = 0, consider_break: bool = True, data_dir: str = None, value_size = 1, second_step = 1, empty_value = 0, invild_value = None):
        if count > 1000:
            raise ValueError("Too many data to read, please reduce the range")
        start_second = TSData.calculate_second(start)
        result = TSData(start, data_dir, value_size, second_step, empty_value, invild_value).read_range_avg(start_second, count, step, consider_break)
        count -= len(result)
        while count > 0:
            start = start + timedelta(days=1)
            temp = TSData(start, data_dir, value_size, second_step, empty_value, invild_value).read_range_avg(0, count, step, consider_break)
            result += temp
            count -= len(temp)
        return result
        


if __name__ == '__main__':
    start_date = datetime.strptime('2025-01-02: 00:30:00', '%Y-%m-%d: %H:%M:%S')
    # 获取当前文件所在目录
    current_dir = os.path.dirname(os.path.abspath(__file__))
    # 获取数据目录
    data_dir = os.path.join(current_dir, '../detection_values/test')

    tsdata = TSData(start_date, data_dir)
    # tsdata.init(value_size=2, second_step=10)
    # for index in range(tsdata.DATA_COUNT_IN_DAY):
    #     # 生成一个0到2^16-1之间的随机数据
    #     data = random.randint(0, tsdata.VALUE_RANGE)
    #     # 将数据写入到文件
    #     tsdata.write(index * tsdata.SECOND_STEP, data)
    # print(f"File {tsdata.file_name} created successfully.")

    begin_time = datetime.now()
    # 读取文件指定秒数位置的数据
    value = tsdata.read(86000)
    print(value)
    tsdata.write(0, 100)
    tsdata.write(10, 200)

    # 读取前50个平均数据，步长为一小时
    start_date = datetime.strptime('2025-01-02: 00:00:00', '%Y-%m-%d: %H:%M:%S')
    values = TSData.read_dates_range_avg(start_date, 49, 20, False, data_dir)
    print('读取50个平均值:', values)

    values = tsdata.read_range(0, 10)
    print('读取前10个数：', str(values))

    # 读取前3个数据的平均值
    values = tsdata.read_avg(0, 3)
    print('前3个数的平均值:', values)

    # 读取一个小时开始的数据
    values = tsdata.read_range_avg(60*60, 10, 60*60, False)
    print('从一个小时时间开始的数据：', values)

    
    end_time = datetime.now()
    delta = end_time - begin_time
    print(f"Time used: {delta.total_seconds()} seconds")


        